﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Kokomo.Mirror.CliMetadata.Tables;
using System.IO;
using Kokomo.Mirror.CliMetadata.Signatures;

namespace Kokomo.Mirror.CliMetadata
{
	class CliCustomAttributeData : CustomAttributeData
	{
		internal static CliCustomAttributeData Parse(
			CliModule module,
			CustomAttributeRow customAttributeRow
			)
		{
			ConstructorInfo constructor = (ConstructorInfo)module.ResolveMethod(customAttributeRow.Type.Token);

			byte[] data = module.Metadata.GetBlob(customAttributeRow.Value);
			if (data == null) return new CliCustomAttributeData(constructor);

			ParameterInfo[] parameters = constructor.GetParameters();
			BitReader reader = new BitReader(data);
			//BinaryReader reader = new BinaryReader(new MemoryStream(data), Encoding.UTF8);
			ushort prolog = reader.ReadUInt16();
			CustomAttributeTypedArgument[] constructorArgs = new CustomAttributeTypedArgument[parameters.Length];
			for (int i = 0; i < parameters.Length; i++)
			{
				ParameterInfo parameter = parameters[i];
				CustomAttributeTypedArgument arg = ReadFixedArg(reader, parameter.ParameterType, module);
				constructorArgs[i] = arg;
			}
			int numNamed = reader.ReadUInt16();
			CustomAttributeNamedArgument[] namedArgs = new CustomAttributeNamedArgument[numNamed];
			for (int i = 0; i < numNamed; i++)
			{
				CustomAttributeNamedArgument arg = ReadNamedArg(reader, constructor.DeclaringType, module);
				namedArgs[i] = arg;
			}

			CliCustomAttributeData attributeData = new CliCustomAttributeData(
				constructor,
				constructorArgs,
				namedArgs
				);
			return attributeData;
		}

		private static CustomAttributeNamedArgument ReadNamedArg(
			BitReader reader,
			Type attributeType,
			CliModule module
			)
		{
			byte flags = reader.ReadByte();
			SignatureElementType elementType = (SignatureElementType)reader.ReadByte();
			string enumName;
			if (elementType == SignatureElementType.Enum)
			{
				enumName = reader.ReadSerString();
			}
			Type memberType;// = (Type)ReadValue(reader, typeof(Type), module);
			string memberName = reader.ReadSerString();
			MemberInfo member;
			if (flags == 0x54)
			{
				PropertyInfo property = attributeType.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				member = property;
				memberType = property.PropertyType;
			}
			else if (flags == 0x53)
			{
				FieldInfo field = attributeType.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				member = field;
				memberType = field.FieldType;
			}
			else
			{
				throw new MalformedSignatureException(null, 0);
			}

			CustomAttributeTypedArgument fixedArg = ReadFixedArg(
				reader,
				memberType,
				module
				);
			return new CustomAttributeNamedArgument(member, fixedArg);
		}

		private static CustomAttributeTypedArgument ReadFixedArg(
			BitReader reader,
			Type argumentType,
			CliModule module
			)
		{
			object value;
			if (argumentType.IsArray)
				value = ReadArrayArg(reader, argumentType.GetElementType(), module);
			else
				value = ReadValue(reader, ref argumentType, module);

			CustomAttributeTypedArgument typedArg = new CustomAttributeTypedArgument(argumentType, value);
			return typedArg;
		}
		private static Array ReadArrayArg(
			BitReader reader,
			Type elementType,
			CliModule module
			)
		{
			uint numElem = reader.ReadUInt32();
			if (numElem == 0xFFFFFFFF) return null;

			Array array = Array.CreateInstance(elementType, numElem);
			for (int i = 0; i < numElem; i++)
			{
				Type type = elementType;
				object value = ReadValue(reader, ref type, module);
				array.SetValue(value, i);
			}

			return array;
		}
		private static object ReadValue(
			BitReader reader,
			ref Type valueType,
			CliModule module
			)
		{
			Type elementType = (valueType.IsEnum)
				? valueType.GetEnumUnderlyingType()
				: valueType
				;

			object value;
			if (MemberEqualityComparer.Equal(elementType, typeof(Type))) value = ReadType(reader, module);
			else if (MemberEqualityComparer.Equal(elementType, typeof(bool))) value = (reader.ReadByte() != 0);
			else if (MemberEqualityComparer.Equal(elementType, typeof(char))) value = (char)reader.ReadUInt16();
			else if (MemberEqualityComparer.Equal(elementType, typeof(float))) value = reader.ReadSingle();
			else if (MemberEqualityComparer.Equal(elementType, typeof(double))) value = reader.ReadDouble();
			else if (MemberEqualityComparer.Equal(elementType, typeof(byte))) value = reader.ReadByte();
			else if (MemberEqualityComparer.Equal(elementType, typeof(sbyte))) value = reader.ReadSByte();
			else if (MemberEqualityComparer.Equal(elementType, typeof(short))) value = reader.ReadInt16();
			else if (MemberEqualityComparer.Equal(elementType, typeof(ushort))) value = reader.ReadUInt16();
			else if (MemberEqualityComparer.Equal(elementType, typeof(int))) value = reader.ReadInt32();
			else if (MemberEqualityComparer.Equal(elementType, typeof(uint))) value = reader.ReadUInt32();
			else if (MemberEqualityComparer.Equal(elementType, typeof(long))) value = reader.ReadInt64();
			else if (MemberEqualityComparer.Equal(elementType, typeof(ulong))) value = reader.ReadUInt64();
			else if (MemberEqualityComparer.Equal(elementType, typeof(string))) value = reader.ReadSerString();
			else if (MemberEqualityComparer.Equal(elementType, typeof(object))) value = ReadObject(reader, module, out valueType);
			else throw new NotSupportedException();

			return value;
		}

		private static Type ReadType(BitReader reader, CliModule module)
		{
			//byte elementType = reader.ReadByte();

			string typeName = reader.ReadSerString();
			Type value = module.GetType(typeName, false, false);
			if ((object)value == null)
				value = module.CliAssembly.LoadContext.GetType(typeName);
			return value;
		}

		private static Type ReadFieldOrPropType(BitReader reader, CliModule module)
		{
			SignatureElementType typeToken = (SignatureElementType)reader.ReadByte();
			Type type;
			if (typeToken == SignatureElementType.SzArray)
			{
				Type elementType = ReadFieldOrPropType(reader, module);
				type = elementType.MakeArrayType();
			}
			else if (typeToken == SignatureElementType.Enum)
			{
				type = ReadType(reader, module);
			}
			else if (typeToken == SignatureElementType.String)
			{
				type = typeof(string);
			}
			else if (typeToken == SignatureElementType.Boxed)
			{
				TypeSpec typeSpec = reader.ReadType();
				type = typeSpec.ResolveType(module, true, null, null);
			}
			else
			{
				throw new MalformedSignatureException(reader.Data, reader.Position);
			}

			return type;
		}
		private static object ReadObject(BitReader reader, CliModule module, out Type type)
		{
			type = ReadFieldOrPropType(reader, module);
			object value = ReadValue(reader, ref type, module);
			return value;
		}

		internal CliCustomAttributeData(
			ConstructorInfo constructor,
			IList<CustomAttributeTypedArgument> constructorArguments = null,
			IList<CustomAttributeNamedArgument> namedArguments = null
			)
		{
			this._constructor = constructor;
			if (constructorArguments == null) constructorArguments = new CustomAttributeTypedArgument[0];
			this._constructorArguments = constructorArguments;
			if (namedArguments == null) namedArguments = new CustomAttributeNamedArgument[0];
			this._namedArguments = namedArguments;
		}

		private ConstructorInfo _constructor;
		public override ConstructorInfo Constructor
		{
			get { return this._constructor; }
		}

		private IList<CustomAttributeTypedArgument> _constructorArguments;
		public override IList<CustomAttributeTypedArgument> ConstructorArguments
		{
			get { return this._constructorArguments; }
		}

		private IList<CustomAttributeNamedArgument> _namedArguments;
		public override IList<CustomAttributeNamedArgument> NamedArguments
		{
			get { return this._namedArguments; }
		}
	}
}
